package engine

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/spf13/cast"

	"gitee.com/zfd81/dbrs/config"
	"gitee.com/zfd81/dbrs/sql/parser"
	"github.com/shopspring/decimal"
	"vitess.io/vitess/go/sqltypes"
	querypb "vitess.io/vitess/go/vt/proto/query"
)

type Type uint32

func (t Type) String() string {
	return Type_name[t]
}

const (
	Type_Databroker Type = iota
	Type_Redis
	Type_Oracle
)

var (
	Type_name = map[Type]string{
		Type_Databroker: "Databroker",
		Type_Redis:      "Redis",
		Type_Oracle:     "Oracle",
	}
)

type Provider interface {
	Name() string
	Type() Type
	Insert(ctx *Context, stmt *parser.InsertStatement) (*sqltypes.Result, error)
	Delete(ctx *Context, stmt *parser.DeleteStatement) (*sqltypes.Result, error)
	Update(ctx *Context, stmt *parser.UpdateStatement) (*sqltypes.Result, error)
	Select(ctx *Context, bindVars map[string]*querypb.BindVariable, callback func(*sqltypes.Result) error, stmt *parser.SelectStatement) error
	ShowTables(ctx *Context, stmt *parser.ShowStatement) (*sqltypes.Result, error)
	ShowCharset(ctx *Context, stmt *parser.ShowStatement) (*sqltypes.Result, error)
	ShowCollation(ctx *Context, stmt *parser.ShowStatement) (*sqltypes.Result, error)
	DescTable(ctx *Context, stmt *parser.DescStatement) (*sqltypes.Result, error)
	AlterTable(ctx *Context, stmt *parser.AlterTableStatement) (*sqltypes.Result, error)
	DropTable(ctx *Context, stmt *parser.DropTableStatement) (*sqltypes.Result, error)
}

func NewProvider(db config.Database) (Provider, error) {
	typ := strings.TrimSpace(db.Type)
	typ = strings.ToLower(typ)
	switch typ {
	case "dbr", "":
		return NewDatabrokerProvider(db)
	case "redis":
		return NewRedisProvider(db)
	case "oracle":
		return NewOracleProvider(db)
	default:
		return nil, fmt.Errorf("%s type database does not support", db.Name)
	}
}

func sqlTypes(val interface{}) (querypb.Type, string) {
	if val == nil {
		return sqltypes.Null, ""
	}
	var typ querypb.Type
	switch v := val.(type) {
	case int, int64:
		typ = sqltypes.Int64
	case string:
		if isInt(v) {
			typ = sqltypes.Int64
		} else if isFloat(v) {
			typ = sqltypes.Float64
		} else {
			typ = sqltypes.VarChar
		}
	case int8:
		typ = sqltypes.Int8
	case int16:
		typ = sqltypes.Int16
	case int32:
		typ = sqltypes.Int32
	case float64:
		typ = sqltypes.Float64
	case float32:
		typ = sqltypes.Float32
	case decimal.Decimal:
		typ = sqltypes.Float64
	case bool:
		typ = sqltypes.Int8
		if v == true {
			val = 1
		} else {
			val = 0
		}
	default:
		typ = sqltypes.VarChar
	}
	return typ, cast.ToString(val)
}

func isInt(str string) bool {
	return regexp.MustCompile(`^\d+$`).MatchString(str)
}

func isFloat(str string) bool {
	return regexp.MustCompile(`^\d+[.]\d+$`).MatchString(str)
}

func isBool(str string) bool {
	return str == "true" || str == "false"
}
